package org.hackystat.storageservice.resources;

import org.hackystat.sensorbase.resource.sensordata.jaxb.SensorData;
import org.hackystat.storageservice.db.derby.DerbyFacade;
import org.hackystat.storageservice.resources.alt.Util;
import org.hackystat.utilities.tstamp.Tstamp;
import org.restlet.Context;
import org.restlet.data.Form;
import org.restlet.data.MediaType;
import org.restlet.data.Request;
import org.restlet.data.Response;
import org.restlet.data.Status;
import org.restlet.resource.Representation;
import org.restlet.resource.Resource;
import org.restlet.resource.Variant;

import javax.xml.bind.JAXBContext;
import javax.xml.bind.JAXBException;
import javax.xml.bind.Unmarshaller;
import javax.xml.datatype.XMLGregorianCalendar;
import java.io.StringReader;

/**
 * The resource for all URIs that extend sensordata. This includes:
 * <ul>
 * <li> host/sensordata/{user}
 * <li> host/sensordata/{user}?sdt={sensordatatype}
 * <li> host/sensordata/{user}?lastModStartTime={lastModTimestamp}&lastModEndTime={lastModTimestamp}
 * <li> host/sensordata/{user}/{timestamp}
 * </ul>
 *
 * @author Philip Johnson
 */
public class UserSensorDataResource extends Resource {

    /**
     * To be retrieved from the URL, or else null if not found.
     */
    private String sdtName;
    /**
     * To be retrieved from the URL, or else null if not found.
     */
    private String timestamp;
    /**
     * To be retrieved from the URL, or else null if not found.
     */
    private String lastModStartTime;
    /**
     * To be retrieved from the URL, or else null if not found.
     */
    private String lastModEndTime;

    private DerbyFacade db;
    private Util util;
    private String user;

    /**
     * Provides the following representational variants: TEXT_XML.
     *
     * @param context  The context.
     * @param request  The request object.
     * @param response The response object.
     */
    public UserSensorDataResource(Context context, Request request, Response response) {
        super(context, request, response);
        this.user = (String) request.getAttributes().get("user");
        this.sdtName = (String) request.getAttributes().get("sensordatatype");
        this.timestamp = (String) request.getAttributes().get("timestamp");
        this.lastModStartTime = (String) request.getAttributes().get("lastModStartTime");
        this.lastModEndTime = (String) request.getAttributes().get("lastModEndTime");
        this.util = new Util(request, response);
        db = (DerbyFacade) getContext().getAttributes().get("DerbyFacade");
        getVariants().clear();
        getVariants().add(new Variant(MediaType.TEXT_XML));
    }

    /**
     * Returns a SensorDataIndex when a GET is called with:
     * <ul>
     * <li> sensordata/{email}
     * <li> sensordata/{email}?sdt={sensordatatype}
     * <li> sensordata/{user}?lastModStartTime={timestamp}&lastModEndTime={timestamp}
     * </ul>
     * Returns a SensorData when a GET is called with:
     * <ul>
     * <li> sensordata/{email}/{timestamp}
     * </ul>
     * <p/>
     * The user must be defined, and the authenticated user must be the uriUser or the Admin or in a
     * project with the uriUser on that day.
     *
     * @param variant The representational variant requested.
     * @return The representation.
     */
    @Override
    public Representation represent(Variant variant) {
        System.out.println("\nUserSensorDataResource.represent");
        // Return index of all data for URI: sensordata/{email}
        if ((this.sdtName == null) && (this.timestamp == null) && (this.lastModStartTime == null)) {
            String xmlData = db.getSensorDataIndex(this.user);
            return Util.getStringRepresentation(xmlData);
        }
        // Return index of data for a given SDT for URI: sensordata/{email}?sdt={sensordatatype}
        if ((this.sdtName != null) && (this.timestamp == null) && (this.lastModStartTime == null)) {
            String xmlData = db.getSensorDataIndex(this.user, this.sdtName);
            return Util.getStringRepresentation(xmlData);
        }
        // Return index of data since the tstamp for URI:
        // sensordata/{user}?lastModStartTime={timestamp}&lastModEndTime={timestamp}
        if ((this.sdtName == null) && (this.timestamp == null) && (this.lastModStartTime != null)) {
            // First, check to see that we can convert the lastModStartTime and EndTime into a tstamp.
            XMLGregorianCalendar lastModStart;
            XMLGregorianCalendar lastModEnd;
            try {
                lastModStart = Tstamp.makeTimestamp(this.lastModStartTime);
            }
            catch (Exception e) {
                util.setStatusBadTimestamp(lastModStartTime);
                return null;
            }
            try {
                lastModEnd = Tstamp.makeTimestamp(this.lastModEndTime);
            }
            catch (Exception e) {
                util.setStatusBadTimestamp(lastModEndTime);
                return null;
            }
            // Now, get the data and return.
            String xmlData = db.getSensorDataIndexLastMod(this.user, lastModStart, lastModEnd);
            return Util.getStringRepresentation(xmlData);
        }
        // Return sensordata representation for URI: sensordata/{email}/{timestamp}
        if ((this.sdtName == null) && (this.timestamp != null) && (this.lastModStartTime == null)) {
            // First, try to parse the timestamp string, and return error if it doesn't parse.
            XMLGregorianCalendar tstamp;
            try {
                tstamp = Tstamp.makeTimestamp(this.timestamp);
            }
            catch (Exception e) {
                util.setStatusBadTimestamp(timestamp);
                return null;
            }
            String xmlData = db.getSensorData(this.user, tstamp);
            // Now, see if we actually have the SensorData.
            if (xmlData == null) { // NOPMD (deeply nested if-then-else)
                util.setStatusMiscError("Unknown sensor data");
                return null;
            }
            // We have the SensorData, so retrieve its xml string representation and return it.
            try {
                return Util.getStringRepresentation(xmlData);
            }
            catch (Exception e) {
                util.setStatusInternalError(e);
                return null;
            }
        }

        // Otherwise we don't understand the URI that they invoked us with.
        util.setStatusMiscError("Request could not be understood.");
        return null;
    }

    /**
     * Indicate the PUT method is supported.
     *
     * @return True.
     */
    @Override
    public boolean allowPut() {
        return true;
    }

    /**
     * Implement the PUT method that creates a new sensor data instance.
     * <ul>
     * <li> The XML must be marshallable into a sensor data instance.
     * <li> The timestamp in the URL must match the timestamp in the XML.
     * <li> The User and SDT must exist.
     * </ul>
     * Note that we are not validating that this sensor data instance contains all of the Required
     * Properties specified by the SDT. This should be done later, on demand, as part of analyses.
     * <p/>
     * We are also not at this point checking to see whether the User and SDT exist.
     *
     * @param entity The XML representation of the new sensor data instance..
     */
    @Override
    public void storeRepresentation(Representation entity) {
        System.out.println("\nUserSensorDataResource.storeRepresentation");
        try {
            Form form = new Form(entity);
            String xmlSensorData = form.getFirstValue("xmlSensorData");
            //System.out.println("\n" + xmlSensorData + "\n");
            String xmlSensorDataRef = form.getFirstValue("xmlSensorDataRef");
            //System.out.println("\n" + xmlSensorDataRef + "\n");
            SensorData sensorData;
            Unmarshaller unmarshaller;
            try {
                unmarshaller = JAXBContext.newInstance(org.hackystat.sensorbase.resource.sensordata.jaxb.ObjectFactory.class).createUnmarshaller();
                sensorData = (SensorData) unmarshaller.unmarshal(new StringReader(xmlSensorData));
            } catch (JAXBException e) {
                System.out.println("ERROR CODE: " + e.getErrorCode());
                System.out.println("MESSAGE     " + e.getMessage());
                util.setStatusInternalError(e);
                return;
            }

            db.storeSensorData(sensorData, xmlSensorData, xmlSensorDataRef);
            getResponse().setStatus(Status.SUCCESS_CREATED);
        }
        catch (RuntimeException e) {
            util.setStatusInternalError(e);
        }
    }

    /**
     * Indicate the DELETE method is supported.
     *
     * @return True.
     */
    @Override
    public boolean allowDelete() {
        return true;
    }

    /**
     * Implement the DELETE method that ensures the specified sensor data instance no longer exists.
     * <ul>
     * <li> The user must exist.
     * <li> The timestamp must be well-formed.
     * <li> The authenticated user must be the uriUser or the admin.
     * </ul>
     * If not timestamp is supplied, then all sensor data will be deleted.
     */
    @Override
    public void removeRepresentations() {
        System.out.println("\nUserSensorDataResource.removeRepresentations");
        XMLGregorianCalendar tstamp;
        try {
            String userEmail = (String) getRequest().getAttributes().get("email");
            if (this.timestamp != null) {
                tstamp = Tstamp.makeTimestamp(this.timestamp);
                db.deleteSensorData(userEmail, tstamp);
            } else {
                db.deleteSensorData(userEmail);
            }
        }
        catch (Exception e) {
            util.setStatusMiscError("Bad timestamp: " + this.timestamp);
            return;
        }
        getResponse().setStatus(Status.SUCCESS_OK);
    }
}
